package sk.ownage.p1.featurestore;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.StreamCorruptedException;
import java.util.ArrayList;
import java.util.List;

import android.graphics.Color;
import android.util.Log;

import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.LatLngBounds;
import com.google.android.gms.maps.model.PolylineOptions;

public class TrackQuadTreeNode {
	final String TAG="TrackQuadTreeNode";
	public LatLngBounds boundingBox;
	int threshold=TrackQuadTreeRoot.actualThreshold;
	int DIRECTORY_MAX=TrackStore.DIRECTORY_MAX;;
	String name;
	public TrackQuadTreeNode northWest,northEast,southWest,southEast;
	public LatLng NW,SE,center;
	int actualThreshold=threshold;
	int size=0;
	boolean divided=false;
	boolean skip=false;
	File trackListFile;
	String path,exactpath;
	int actual_segment=0;
	LatLng last_waypoint=null;
	TrackReader tr;
	TrackQuadTreeRoot rootNode;
	LatLng old_wp=null,actual_wp=null,new_wp=null;
	double minLat=85.0,maxLat=-85.0,minLng=180.0,maxLng=-179.999999;
	
	public TrackQuadTreeNode(TrackQuadTreeRoot _rootNode,String _name, LatLngBounds bounding,String _path, LatLngBounds actualListBounds){
		rootNode=_rootNode;
		boundingBox=bounding;
		name=_name;
		path=_path;
		exactpath=path+"/"+"list.bin";
		NW=new LatLng(boundingBox.northeast.latitude, boundingBox.southwest.longitude);
		SE=new LatLng( boundingBox.southwest.latitude, boundingBox.northeast.longitude);
		center=getCenter(boundingBox);
		
		size=loadSize();
		File folder=new File(path);
		//ak do tejto vetvy este mozeme pridavat body
		if(!skip){
			if(!folder.exists()){
				folder.mkdirs();
			}
			trackListFile=new File(exactpath);
			if(!trackListFile.exists())
				try {
					FileOutputStream fos = new FileOutputStream(trackListFile);
					ObjectOutputStream os = new ObjectOutputStream(fos);
					os.close();
				} catch (FileNotFoundException e) {
					Log.e(TAG, e.toString());
					e.printStackTrace();
				} catch (IOException e) {
					Log.e(TAG, e.toString());
					e.printStackTrace();
				}
		}
		//ak v skutocnosti uz existuju vetvy tejto vetvy
		if(boundingBox.contains(actualListBounds.southwest)&& boundingBox.contains(actualListBounds.northeast)){
			northEast=new TrackQuadTreeNode(rootNode,name+">NE", new LatLngBounds(center,boundingBox.northeast),path+"/0",actualListBounds);
			northWest=new TrackQuadTreeNode(rootNode,name+">NW", new LatLngBounds(new LatLng(center.latitude, boundingBox.southwest.longitude), new LatLng(boundingBox.northeast.latitude, center.longitude)),path+"/1",actualListBounds);
			southEast=new TrackQuadTreeNode(rootNode,name+">SE", new LatLngBounds(new LatLng(boundingBox.southwest.latitude, center.longitude), new LatLng(center.latitude,boundingBox.northeast.longitude)),path+"/2",actualListBounds);
			southWest=new TrackQuadTreeNode(rootNode,name+">SW", new LatLngBounds(boundingBox.southwest,center),path+"/3",actualListBounds);
			divided=true;
			size=actualThreshold;
			saveSize();
		}
	}
	
	private LatLng getCenter(LatLngBounds bbox){
		double averageLat=(bbox.northeast.latitude+bbox.southwest.latitude)/2;
		double averageLon=(bbox.northeast.longitude+bbox.southwest.longitude)/2;
		return new LatLng(averageLat, averageLon);
	}
	
	public void addWaypoint(LatLng wp,int new_segment){
		if(!skip){
			size=loadSize();
		}
		ObjectInputStream is;
		if(size<actualThreshold && !divided){
			if(minLat>wp.latitude)
				minLat=wp.latitude;
			if(maxLat<wp.latitude)
				maxLat=wp.latitude;
			if(minLng>wp.longitude)
				minLng=wp.longitude;
			if(maxLng<wp.longitude)
				maxLng=wp.longitude;
			try {
				FileOutputStream fos = new FileOutputStream(trackListFile, true);
				AppendingObjectOutputStream os= new AppendingObjectOutputStream(new BufferedOutputStream(fos));
				os.writeByte(new_segment);
				os.writeDouble(wp.latitude);
				os.writeDouble(wp.longitude);
				os.close();
			} catch (FileNotFoundException e) {
				Log.e(TAG, e.toString());
				e.printStackTrace();
			} catch (IOException e) {
				Log.e(TAG, e.toString());
				e.printStackTrace();
			}
			size++;
			saveSize();
		}
		else if(!divided){
			LatLngBounds actualListBounds= new LatLngBounds(new LatLng(minLat, minLng), new LatLng(maxLat, maxLng));
			northEast=new TrackQuadTreeNode(rootNode,name+">NE", new LatLngBounds(center,boundingBox.northeast),path+"/0",actualListBounds);
			northWest=new TrackQuadTreeNode(rootNode,name+">NW", new LatLngBounds(new LatLng(center.latitude, boundingBox.southwest.longitude), new LatLng(boundingBox.northeast.latitude, center.longitude)),path+"/1",actualListBounds);
			southEast=new TrackQuadTreeNode(rootNode,name+">SE", new LatLngBounds(new LatLng(boundingBox.southwest.latitude, center.longitude), new LatLng(center.latitude,boundingBox.northeast.longitude)),path+"/2",actualListBounds);
			southWest=new TrackQuadTreeNode(rootNode,name+">SW", new LatLngBounds(boundingBox.southwest,center),path+"/3",actualListBounds);
			try {
				FileInputStream fis = new FileInputStream(trackListFile);
				is = new ObjectInputStream(new BufferedInputStream(fis));
				int count=size;
				while (count >0) {
					int new_seg=is.readByte();
					double latitude1=is.readDouble(),longitude1=is.readDouble();
					LatLng waypoint=new LatLng(latitude1, longitude1);
					old_wp=new_wp;
					new_wp=waypoint;
					actual_wp=old_wp;
					divideQTree(old_wp,actual_wp,new_wp,null,new_seg);
					count--;
				}
				is.close();
				old_wp=new_wp;
				new_wp=wp;
				actual_wp=old_wp;
				divideQTree(old_wp,actual_wp,new_wp,null,new_segment);
				trackListFile.delete();
			} catch (StreamCorruptedException e) {
				Log.e(TAG, e.toString());
				e.printStackTrace();
			} catch (IOException e) {
				Log.e(TAG, e.toString());
				e.printStackTrace();
			}
			divided=true;
			skip=true;
			saveSize();
		}
		else{
			old_wp=new_wp;
			new_wp=wp;
			actual_wp=old_wp;
			addWaypointToSubtree(wp,new_segment);
		}
	}
	
	private void addWaypointToSubtree(LatLng wp,int new_segment){
		if(wp.latitude>=center.latitude){
			if(wp.longitude>=center.longitude){
				northEast.addWaypoint(wp,new_segment);
			}
			else{
				northWest.addWaypoint(wp,new_segment);
			}
		}
		else{
			if(wp.longitude>=center.longitude){
				southEast.addWaypoint(wp,new_segment);
			}
			else{
				southWest.addWaypoint(wp,new_segment);
			}
		}	
	}
	
	private void divideQTree(LatLng old_wp,LatLng actual_wp,LatLng new_wp,TrackQuadTreeNode where,int new_segment){
		
		if(old_wp!=null && new_segment!=1 ){

			if(where==null){
				if(actual_wp.latitude>=center.latitude){
					if(actual_wp.longitude>=center.longitude){
						where=northEast;
					}
					else{
						where=northWest;
					}
				}
				else{
					if(actual_wp.longitude>=center.longitude){
						where=southEast;
					}
					else{
						where=southWest;
					}
				}
			}
			
			if(where==northEast){
				LatLng westSide=getGeoPoint(northEast.NW, new LatLng(northEast.SE.latitude, northEast.NW.longitude), actual_wp, new_wp);
				if(westSide!=null){
					northEast.addWaypoint(westSide,0);
					northWest.addWaypoint(new LatLng(westSide.latitude, westSide.longitude-0.0000001),1);
					divideQTree(old_wp, new LatLng(westSide.latitude, westSide.longitude-0.0000001), new_wp,northWest,0);
					}
				//northest quadrant ->> south bound
				LatLng southSide=getGeoPoint(northEast.SE, new LatLng(northEast.SE.latitude, northEast.NW.longitude), actual_wp, new_wp);
				if(southSide!=null){
					northEast.addWaypoint(southSide,0);
					southEast.addWaypoint(new LatLng(southSide.latitude-0.0000001, southSide.longitude),1);
					divideQTree(old_wp, new LatLng(southSide.latitude-0.0000001, southSide.longitude), new_wp,southEast,0);
					}
				if(southSide==null && westSide==null)
					northEast.addWaypoint(new_wp,0);
			}
			if(where==northWest){
				LatLng eastSide=getGeoPoint(new LatLng(northWest.NW.latitude, northWest.SE.longitude), northWest.SE, actual_wp, new_wp);
				if(eastSide!=null){
					northWest.addWaypoint(eastSide,0);
					northEast.addWaypoint(new LatLng(eastSide.latitude, eastSide.longitude+0.0000001),1);
					divideQTree(old_wp, new LatLng(eastSide.latitude, eastSide.longitude+0.0000001), new_wp,northEast,0);
					}
				//northWest quadrant ->> south bound
				LatLng southSide=getGeoPoint(northWest.SE, new LatLng(northWest.SE.latitude, northWest.NW.longitude), actual_wp, new_wp);
				if(southSide!=null){
					northWest.addWaypoint(southSide,0);
					southWest.addWaypoint(new LatLng(southSide.latitude-0.0000001, southSide.longitude),1);
					divideQTree(old_wp, new LatLng(southSide.latitude-0.0000001, southSide.longitude), new_wp,southWest,0);
					}
				if(southSide==null && eastSide==null)
					northWest.addWaypoint(new_wp,0);
						}
			if(where==southEast){
				LatLng westSide=getGeoPoint(southEast.NW, new LatLng(southEast.SE.latitude, southEast.NW.longitude), actual_wp, new_wp);
				if(westSide!=null){
					southEast.addWaypoint(westSide,0);
					southWest.addWaypoint(new LatLng(westSide.latitude, westSide.longitude-0.0000001),1);
					divideQTree(old_wp, new LatLng(westSide.latitude, westSide.longitude-0.0000001), new_wp,southWest,0);
					}
				//southEast quadrant ->> north bound
				LatLng northSide=getGeoPoint(southEast.NW, new LatLng(southEast.NW.latitude, southEast.SE.longitude), actual_wp, new_wp);
				if(northSide!=null){
					southEast.addWaypoint(northSide,0);
					northEast.addWaypoint(new LatLng(northSide.latitude+0.0000001, northSide.longitude),1);
					divideQTree(old_wp, new LatLng(northSide.latitude+0.0000001, northSide.longitude), new_wp,northEast,0);
					}
				if(northSide==null && westSide==null)
					southEast.addWaypoint(new_wp,0);
						}
			if(where==southWest){
				//Log.d(TAG,"qdvide >>>> southWest"+southWest.name);
				LatLng eastSide=getGeoPoint(new LatLng(southWest.NW.latitude, southWest.SE.longitude), southWest.SE, actual_wp, new_wp);
				if(eastSide!=null){
					southWest.addWaypoint(eastSide,0);
					southEast.addWaypoint(new LatLng(eastSide.latitude, eastSide.longitude+0.0000001),1);
					divideQTree(old_wp, new LatLng(eastSide.latitude, eastSide.longitude+0.0000001), new_wp,southEast,0);
					}
				//southWest quadrant ->> north bound
				LatLng northSide=getGeoPoint(southWest.NW, new LatLng(southWest.NW.latitude, southWest.SE.longitude), actual_wp, new_wp);
				if(northSide!=null){
					southWest.addWaypoint(northSide,0);
					northWest.addWaypoint(new LatLng(northSide.latitude+0.0000001, northSide.longitude),1);
					divideQTree(old_wp, new LatLng(northSide.latitude+0.0000001, northSide.longitude), new_wp,northWest,0);
					}
				if(northSide==null && eastSide==null)
					southWest.addWaypoint(new_wp,0);
			}
		}else{
			addWaypointToSubtree(new_wp,new_segment);
		}
		
	}
	
	public void divideQTree2(LatLng old_wp,LatLng actual_wp,LatLng boundary_wp,LatLng new_wp){
		
	}
	public TrackQuadTreeNode getLastUsedTreeNode(LatLng old_wp){
		if(northEast!=null && northWest!=null && northEast!=null && southWest!=null){
			if(old_wp.latitude>=center.latitude){
				if(old_wp.longitude>=center.longitude){
					return northEast.getLastUsedTreeNode(old_wp);
				}
				else{
					return northWest.getLastUsedTreeNode(old_wp);
				}
			}
			else{
				if(old_wp.longitude>=center.longitude){
					return southEast.getLastUsedTreeNode(old_wp);
				}
				else{
					return southWest.getLastUsedTreeNode(old_wp);
				}
			}
		}else
			return this;
	}
	//ci sa cislo nachadza v danom intervale
    private boolean InInterval(double what,int from,int to){
    	return (what>=from)&&(what<=to);
    }
	 //najde prisecnik(GeoPoint ktory pretina hranice daneho stvorca QuadTreeNodu a cesty, ktora prechadza z jedneho stvorca do ineho)
    private LatLng getGeoPoint(LatLng a,LatLng b,LatLng c, LatLng d){
    	double x1=a.longitude,x2=b.longitude,x3=c.longitude,x4=d.longitude,
    		y1=a.latitude,y2=b.latitude,y3=c.latitude,y4=d.latitude;
    	double ua,ub,ca,cb;
    	double menovatel=(y4-y3)*(x2-x1)-(x4-x3)*(y2-y1);
    	if (menovatel==0){
    		return null;
    	}
    	else{
    		ca=((x4-x3)*(y1-y3)-(y4-y3)*(x1-x3));
    		cb=((x2-x1)*(y1-y3)-(y2-y1)*(x1-x3));
    		ua=ca/menovatel;
    		ub=cb/menovatel;
    		if(InInterval(ua, 0, 1) && InInterval(ub, 0, 1)){
    			return new LatLng((y1+ua*(y2-y1)),(x1+ua*(x2-x1)));
    		}
    		else{
    			return null;
    		}
    	}
    }
	
	public List<PolylineOptions> getPolylines(LatLngBounds boundingBox1,String _path,GoogleMap gmap){
		return getPolylines(boundingBox1,_path,boundingBox.southwest,boundingBox.northeast,gmap);
	}
	
	
	public List<PolylineOptions> getPolylines(LatLngBounds boundingBox1,String _path,LatLng _SW,LatLng _NE,GoogleMap gmap){
		
		double averageLat=(_SW.latitude+_NE.latitude)/2;
		double averageLon=(_SW.longitude+_NE.longitude)/2;
		LatLng tempCenter=new LatLng(averageLat, averageLon);
		
		if(tempCenter.longitude>90 || true)
		{
			PolylineOptions temp =new PolylineOptions();
			temp.add(_SW);
			temp.add(new LatLng(_SW.latitude, _NE.longitude));
			temp.add(_NE);
			temp.add(new LatLng(_NE.latitude, _SW.longitude));
			temp.add(_SW);
			temp.color(Color.rgb((int)Math.round(Math.random()*255),(int) Math.round(Math.random()*255), (int)Math.round(Math.random()*255)));
			gmap.addPolyline(temp);
		}
		
		
		List<PolylineOptions> result=new ArrayList<PolylineOptions>();
		
		LatLngBounds bbox2=new LatLngBounds(_SW, _NE);
		boolean condition=(boundingBox1.contains(new LatLng(_NE.latitude, _SW.longitude)) || boundingBox1.contains(new LatLng(_SW.latitude, _NE.longitude)) || boundingBox1.contains(_NE) || boundingBox1.contains(_SW)
					|| boundingBox1.contains(tempCenter)|| bbox2.contains(boundingBox1.northeast) || bbox2.contains(boundingBox1.southwest) || bbox2.contains(new LatLng(boundingBox1.southwest.latitude, boundingBox1.northeast.longitude)) || bbox2.contains(new LatLng(boundingBox1.northeast.latitude,boundingBox1.southwest.longitude)));
		if(!condition)
			return result;
		FileInputStream fis;
		ObjectInputStream is;
		if(_path.equals(""))
			_path=path;
		File sizePath=new File(_path+"/"+"size.bin");
		int flag=0;
		if(sizePath.exists())
			try {
				fis = new FileInputStream(sizePath);
				 is= new ObjectInputStream(fis);
				flag=is.readInt();
				is.close();
			} catch (Exception e) {
				Log.d(TAG, e.toString());
			}
		try {
				File inputStream=new File(_path+"/"+"list.bin");
				fis = new FileInputStream(inputStream);
				is = new ObjectInputStream(fis);
				PolylineOptions tempPolylineOptions=new PolylineOptions();
				while (flag >0) {
					tempPolylineOptions.add(new LatLng(is.readDouble(), is.readDouble()));
					flag--;
				}
				result.add(tempPolylineOptions);
			} catch (EOFException e) {
				Log.e(TAG, e.toString());
				e.printStackTrace();
			}catch (FileNotFoundException e) {
				Log.e(TAG, e.toString());
				e.printStackTrace();
			} catch (StreamCorruptedException e) {
				Log.e(TAG, e.toString());
				e.printStackTrace();
			} catch (IOException e) {
				Log.e(TAG, e.toString());
				e.printStackTrace();
			}
		return result;
	}

	private int loadSize(){
		File sizePath=new File(path+"/"+"size.bin");
		int result=0;
		if(sizePath.exists())
		try {
			FileInputStream fis = new FileInputStream(sizePath);
			ObjectInputStream is = new ObjectInputStream(new BufferedInputStream(fis));
			result=is.readInt();
			divided=is.readBoolean();
			skip=is.readBoolean();
			actualThreshold=is.readInt();
			is.close();
		} catch (Exception e) {
			Log.d(TAG, e.toString());
		}
		return result;
	}
	
	private void saveSize(){
		File sizePath=new File(path+"/"+"size.bin");
		try {
			FileOutputStream fos = new FileOutputStream(sizePath);
			ObjectOutputStream os = new ObjectOutputStream(new BufferedOutputStream(fos));
			os.writeInt(size);
			os.writeBoolean(divided);
			os.writeBoolean(skip);
			os.writeInt(threshold);
			os.close();
		} catch (Exception e) {
			Log.d(TAG, e.toString());
		}
	}
	
}

